-- vim:set ft=lua:
dofile(mg.script_name:gsub('[^\\/]*$','')..'util.lua')

-- トランスコードするかどうか(する場合はreadex.exeとffmpeg.exeを用意すること)
XCODE=GetVarInt(mg.request_info.query_string, 'xcode',0,1)~=0
-- デフォルト設定
-- フィルタオプション
XFILTER='-vf yadif=0:-1:1'
XFILTER_CINEMA='-vf pullup -r 24000/1001'
-- ffmpeg変換オプション($FILTERはフィルタオプションに置換)
-- libvpxの例:リアルタイム変換と画質が両立するようにビットレート-bと計算量-cpu-usedを調整する
XOPT='-vcodec libvpx -b:v 896k -quality realtime -cpu-used 1 $FILTER -s 512x288 -acodec libvorbis -ab 128k -f webm -'
XOPT_mp4='-vcodec libx264 -profile:v main -level 31 -b:v 896k -maxrate 4M -bufsize 4M -preset veryfast -g 120 $FILTER -s 512x288 -acodec aac -ab 128k -f mp4 -movflags frag_keyframe+empty_moov -'
XOPT_nvenc='-vcodec h264_nvenc -profile:v main -level 31 -b:v 1408k -maxrate 8M -bufsize 8M -preset medium -g 120 $FILTER -s 1280x720 -acodec aac -ab 128k -f mp4 -movflags frag_keyframe+empty_moov -'
-- 出力バッファの量(bytes。asyncbuf.exeを用意すること。変換負荷や通信のむらを吸収する)
XBUF=0
-- 転送開始前に変換しておく量(bytes)
XPREPARE=0

-- コマンドはEDCBのToolsフォルダにあるものを優先する
ini='Setting\\HttpPublic.ini'
tools=edcb.GetPrivateProfile('SET','ModulePath','','Common.ini')..'\\Tools\\'
ffmpeg=edcb.GetPrivateProfile('SET','ffmpeg',tools..'ffmpeg.exe',ini)
ffprobe=edcb.GetPrivateProfile('SET','ffprobe',tools..'ffprobe.exe',ini)
readex=edcb.GetPrivateProfile('SET','readex',tools..'readex.exe',ini)
asyncbuf=edcb.GetPrivateProfile('SET','asyncbuf',tools..'asyncbuf.exe',ini)

-- 設定読み込み
XOPT=edcb.GetPrivateProfile('MOVIE',(mg.get_var(mg.request_info.query_string,'quality') or ''),XOPT,ini)
XBUF=tonumber(edcb.GetPrivateProfile('SET','XBUF',XBUF,ini))
-- mp4判定 (XOPTにmp4が含まれているはず)
mp4=XOPT:match('mp4')
-- iosの場合強制的にmp4
if Check_iOS() and not mp4 then
  XOPT=nvenc and XOPT_nvenc or XOPT_mp4
  mp4=true
end
XEXT=mp4 and '.mp4' or '.webm'

r=edcb.GetRecFilePath((GetVarInt(mg.request_info.query_string,'reid') or 0))
v=edcb.GetRecFileInfo((GetVarInt(mg.request_info.query_string,'id') or 0))

if r then
  fpath=r
  if XCODE then
    -- トランスコード中のファイルがあればそっち使う
    f=edcb.FindFile(fpath..XEXT, 1)
    if f then
      fpath=fpath..XEXT
      XCODE=false
    end
  end
elseif v then
  fpath=v.recFilePath
else
  fpath=nil
  faddr=mg.get_var(mg.request_info.query_string,'fname')
  if faddr then
    if mg.get_var(mg.request_info.query_string,'public') then
      fpath=DocumentToNativePath(faddr)
    else
      -- 冗長表現の可能性を潰す
      faddr=edcb.Convert('utf-8','utf-8',faddr):gsub('[\\/]+','\\')
      for i,v in ipairs(GetLibraryPathList()) do
        -- ライブラリ配下にあるか＋禁止文字と正規化のチェック
        v=(v..'\\'):gsub('[\\/]+','\\')
        if faddr:sub(1,#v):lower()==v:lower() and not faddr:sub(#v+1):find('[\0-\x1f\x7f:*?"<>|]') and not faddr:sub(#v+1):find('%.\\') then
          fpath=faddr
          library=true
          break
        end
      end
    end
  end
end

if fpath then
  offset=tonumber(mg.get_var(mg.request_info.query_string,'offset')) or 0
  audio2=GetVarInt(mg.request_info.query_string,'audio2',0,1) or 0
  dual=GetVarInt(mg.request_info.query_string,'dual',0,2)
  dual=dual==1 and ' -dual_mono_mode main' or dual==2 and ' -dual_mono_mode sub' or ''
  filter=GetVarInt(mg.request_info.query_string,'cinema')==1 and XFILTER_CINEMA or XFILTER
end

f=nil
if fpath then
  fname='xcode'..(fpath:match('%.[0-9A-Za-z]+$') or '')
  fnamets='xcode'..edcb.GetPrivateProfile('SET','TSExt','.ts','EpgTimerSrv.ini'):lower()
  -- 拡張子を限定
  if fname:lower()==fnamets or library then
    f=edcb.io.open(fpath, 'rb')
    if f then
      meta=mg.get_var(mg.request_info.query_string,'meta')
      if meta then
        ff=edcb.FindFile and edcb.FindFile(ffprobe, 1)
        if ff then
          ff=edcb.io.popen('""'..ffprobe..'" -i "'..fpath..'"  -hide_banner -show_entries format 2>&1"', 'rb'):read('*a')
          meta={duration=ff:match('duration=(.-)\r\n'), audio={}}
          fsize=ff:match('size=(.-)\r\n')
          offset=math.floor(fsize*10/100/188)*188
          ff=edcb.io.popen('""'..readex..'" '..offset..' 4 "'..fpath..'" | "'
            ..ffprobe..'" -i pipe:0 -hide_banner -show_format 2>&1"', 'rb'):read('*a')
          for v in ff:gmatch(':%sAudio:%s(.-)\r\n') do
             table.insert(meta.audio,v)
          end
        else
          meta={duration=GetDurationSec(f)}
        end
        f:close()
      else
        fsec,fsize=GetDurationSec(f,fpath)
        if offset~=0 and offset~=100 and fsec>0 and SeekSec(f,fsec*offset/100) then
          offset=f:seek('cur',0) or 0
        else
          offset=math.floor(fsize*offset/100/188)*188
        end
        if XCODE then
          f:close()
          -- 容量確保の可能性があるときは周期188+同期語0x47(188*256+0x47=48199)で対象ファイルを終端判定する
          sync=edcb.GetPrivateProfile('SET','KeepDisk',0,'EpgTimerSrv.ini')~='0'
          f=edcb.io.popen('""'..readex..'" '..offset..(sync and ' 4p48199' or ' 4')..' "'..fpath..'" | "'
            ..ffmpeg..'"'..dual..' -i pipe:0 -map 0:v:0 -map 0:a:'..audio2..' '..XOPT:gsub('$FILTER',filter)
            ..(XBUF>0 and ' | "'..asyncbuf..'" '..XBUF..' '..XPREPARE or '')..'"', 'rb')
          fname='xcode'..XEXT
        else
          -- 容量確保には未対応
          f:seek('set', offset)
        end
      end
    end
  end
end

debug=GetVarInt(mg.request_info.query_string,'debug',0,5) or 0
if debug>0 then
  if f then
    ct='<success>変換成功</success><errorcode>'
      ..({'MEDIA_ERR_ABORTED','MEDIA_ERR_NETWORK','MEDIA_ERR_DECODE','MEDIA_ERR_SRC_NOT_SUPPORTED','NETWORK_NO_SOURCE'})[debug]..'</errorcode>'
  else
    ct='<error>変換失敗</error>'
  end
  ct='<?xml version="1.0" encoding="UTF-8" ?'..'><entry>'..ct..'<fpath>'
    ..fpath..'</fpath>'
    ..(edcb.FindFile(fpath, 1) and '' or '<notfound>404</notfound>')..'<ffmpeg>'
    ..(edcb.io.popen('""'..ffmpeg..'" -version"'):read('*a'):match('(ffmpeg version .-)%s') or 'NG')..'</ffmpeg><ffprobe>'
    ..(edcb.FindFile(ffprobe, 1) and 'OK' or 'NG')..'</ffprobe><buffer>'
    ..XBUF..'bytes'
    ..(XBUF>0 and '<asyncbuf>'..(edcb.FindFile(asyncbuf, 1) and 'OK' or 'NG')..'</asyncbuf>' or '')
    ..'</buffer><XCODE>'
  if XCODE then
    ct=ct..'<XOPT>'
      ..XOPT:gsub('$FILTER',(filter or ''))..'</XOPT><offset>'
      ..(offset or '')..'</offset><audio2>'
      ..(audio2 or '')..'</audio2><dual>'
      ..(dual or '')..'</dual>'
  else
    ct=ct..'NO'
  end
  ct=ct..'</XCODE></entry>'
  mg.write(Response(200,'text/xml', 'utf-8', #ct) ..'\r\n', ct)
elseif meta then
  ct='<?xml version="1.0" encoding="UTF-8" ?><entry>'..(type(meta)=='table' and (meta.duration~='N/A' and '<duration>'..meta.duration..'</duration>' or '')..'<audio>'..#meta.audio..'</audio>' or '<err>なし</err>')..'</entry>'
  mg.write(Response(200,'text/xml', 'utf-8', #ct) ..'\r\n', ct)
elseif not f then
  mg.write(Response(404,'text/html','utf-8')..'\r\n')
else
  mg.write(Response(200,mg.get_mime_type(fname))..'Content-Disposition: filename='..fname..'\r\n\r\n')
  retry=0
  while true do
    buf=f:read(48128)
    if buf and #buf ~= 0 then
      retry=0
      if not mg.write(buf) then
        -- キャンセルされた
        mg.cry('canceled')
        break
      end
    else
      -- 終端に達した。4秒間この状態が続けば対象ファイルへの追記が終息したとみなす
      retry=retry+1
      if XCODE or retry > 20 then
        mg.cry('end')
        break
      end
      edcb.Sleep(200)
    end
  end
  f:close()
end
